// Dynamic tree editor - tree-like data structures editor for .NET/Mono.
// Copyright (C) 2006  Vladimir Sibirov
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

using System;
using System.Data;
//using System.Data.OleDb;
using System.Collections;

namespace TreeEditor
{
	/// <summary>
	/// Complete tree node class with left/right indeces that allow you to select all the node descendants with just one query.
	/// </summary>
	public class Node
	{
		/// <summary>
		/// Path delimiter string.
		/// </summary>
		private const string PATH_DELIMITER = "/";

		/// <summary>
		/// Unique identifier.
		/// </summary>
		private uint id;

		/// <summary>
		/// Parent node indentifier.
		/// </summary>
		private uint pid;
		
		/// <summary>
		/// Left index.
		/// </summary>
		private uint left;
		
		/// <summary>
		/// Right index.
		/// </summary>
		private uint right;
		
		/// <summary>
		/// Number of levels from root (starting at 0).
		/// </summary>
		private uint depth;

		/// <summary>
		/// Node data class identifier.
		/// </summary>
		private uint cid;

		/// <summary>
		/// Node caption.
		/// </summary>
		private string name;
		
		/// <summary>
		/// Path from root.
		/// </summary>
		private string path;

		/// <summary>
		/// Whether the node is leaf (without children).
		/// </summary>
		private bool leaf;

		/// <summary>
		/// The tree this node belongs to.
		/// </summary>
		private Tree tree;

		/// <summary>
		/// Dataset link.
		/// </summary>
		private DataSet data;

		/// <summary>
		/// The class this node belongs to.
		/// </summary>
		private Class parentClass;

		/// <summary>
		/// User-defined data container.
		/// </summary>
		private SortedList fields;

		/// <summary>
		/// Node is new flag (yet unused).
		/// </summary>
		private bool isNew;

		/*/// <summary>
		/// Class has been changed flag.
		/// </summary>
		private bool classChanged;*/

		/// <summary>
		/// Blank node constructor.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		public Node(Tree tree)
		{
			this.tree = tree;
			data = tree.Data;
			fields = new SortedList();
		}

		/// <summary>
		/// Loads the node from the tree dataset by identifier.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		/// <param name="id">Unique node identifier.</param>
		public Node(Tree tree, uint id)
		{
			fields = new SortedList();
			this.tree = tree;
			data = tree.Data;
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("id=" + id.ToString());
			if(rows.Length == 1)
			{
				// Setting basic params
				this.id = id;
				pid = uint.Parse(rows[0]["pid"].ToString());
				left = uint.Parse(rows[0]["left"].ToString());
				right = uint.Parse(rows[0]["right"].ToString());
				depth = uint.Parse(rows[0]["depth"].ToString());
				cid = uint.Parse(rows[0]["cid"].ToString());
				name = rows[0]["name"].ToString();
				path = rows[0]["path"].ToString();
				leaf = bool.Parse(rows[0]["leaf"].ToString());
				// Class-specific actions
				if(cid > 0)
				{
					parentClass = new Class(cid, tree);
					// Loading user-defined fields
					rows = data.Tables[parentClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length == 1)
					{
						for(int i = 0; i < parentClass.Columns.Count; i++)
						{
							string key = ((Column) parentClass.Columns.GetByIndex(i)).Name;
							fields.Add(key, rows[0][key]);
						}
					}
				}
			}
			else
			{
				this.id = 0;
				// Don't forget the id for 0 then!
			}
		}

		/// <summary>
		/// Creates a root node. Use to construct a root node only.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		/// <param name="name">Node name</param>
		public Node(Tree tree, string name)
		{
			this.name = name;
			leaf = true;
			this.tree = tree;
			data = tree.Data;
			pid = 0;
			cid = 0;
			fields = new SortedList();
			isNew = true;
			// Other stuff is 0 or recalculated on Add()
		}

		/// <summary>
		/// Constructs a new node with relation to its parent.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		/// <param name="name">Node name.</param>
		/// <param name="pid">Parent node identifier.</param>
		public Node(Tree tree, string name, uint pid)
		{
			this.pid = pid;
			this.name = name;
			this.tree = tree;
			data = tree.Data;
			leaf = true;
			cid = 0;
			fields = new SortedList();
			isNew = true;
		}

		/// <summary>
		/// Constructs a new node with relation to its parent, instancing the exact class.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		/// <param name="name">Node name.</param>
		/// <param name="pid">Parent node identifier.</param>
		/// <param name="cid">Node data class identfier</param>
		public Node(Tree tree, string name, uint pid, uint cid)
		{
			this.pid = pid;
			this.name = name;
			this.tree = tree;
			data = tree.Data;
			leaf = true;
			this.cid = cid;
			if(cid > 0)
			{
				parentClass = new Class(cid, tree);
				// Prepare the fields
				fields = new SortedList();
				for(int i = 0; i < parentClass.Columns.Count; i++)
				{
					string key = ((Column) parentClass.Columns[i]).Name;
					fields.Add(key, null);
				}
			}
			isNew = true;
		}

		/// <summary>
		/// Constructs a new node with relation to its parent, instancing the exact class.
		/// </summary>
		/// <param name="tree">The tree this node belongs to.</param>
		/// <param name="name">Node name.</param>
		/// <param name="pid">Parent node identifier.</param>
		/// <param name="nodeClass">Class object for the node.</param>
		public Node(Tree tree, string name, uint pid, Class nodeClass)
		{
			this.pid = pid;
			this.name = name;
			this.tree = tree;
			data = tree.Data;
			leaf = true;
			if(nodeClass != null)
			{
				this.cid = nodeClass.Id;
				parentClass = nodeClass;
				// Prepare the fields
				fields = new SortedList();
				for(int i = 0; i < parentClass.Columns.Count; i++)
				{
					string key = ((Column) parentClass.Columns[i]).Name;
					fields.Add(key, null);
				}
			}
			isNew = true;
		}

		/// <summary>
		/// Node identifier.
		/// </summary>
		public uint Id
		{
			get
			{
				return id;
			}
		}

		/// <summary>
		/// Node name
		/// </summary>
		public string Name
		{
			get
			{
				return name;
			}
			set
			{
				name = value;
			}
		}
		
		/// <summary>
		/// Read-only class identifier.
		/// </summary>
		public uint ClassId
		{
			get
			{
				return cid;
			}
		}

		/// <summary>
		/// Parent node.
		/// </summary>
		public Node Parent
		{
			get
			{
				if(pid == 0)
					return null;
				else
					return new Node(tree, pid);
			}
		}

		/// <summary>
		/// The class this node belongs to.
		/// </summary>
		public Class ParentClass
		{
			get
			{
				return parentClass;
			}
			set
			{
				if(id > 0 && cid > 0 && value != null && cid != value.Id)
				{
					// Changing the class of existing object
					Class oldClass = new Class(cid, tree);
					// Copying the fields that exist in both old and new classes,
					// matching both name and data type.
					SortedList newFields = new SortedList();
					for(int i = 0; i < value.Columns.Count; i++)
					{
						Column col = (Column) value.Columns.GetByIndex(i);
						if(fields.ContainsKey(col.Name) && col.DataType == ((Column) oldClass.Columns[col.Name]).DataType)
						{
							// Copy a field
							newFields.Add(col.Name, fields[col.Name]);
						}
						else
						{
							// Leave it blank
							newFields.Add(col.Name, null);
						}
					}
					// Something ever called "swap vectors" :)
					fields = newFields;
					// Removing the old class instance
					DataRow[] rows = data.Tables[oldClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length == 1)
						rows[0].Delete();
					// Creating a new class instance and filling up the fields
					DataRow row = data.Tables[value.Name].NewRow();
					row[Tree.NODE_ID] = id;
					for(int i = 0; i < fields.Count; i++)
					{
						row[fields.GetKey(i).ToString()] = fields.GetByIndex(i);
					}
					data.Tables[value.Name].Rows.Add(row);
					// Done
					data.AcceptChanges();
				}
				else if(id == 0 && cid == 0 && value != null)
				{
					// Setting a class for new object
					fields = new SortedList();
					for(int i = 0; i < value.Columns.Count; i++)
					{
						string key = ((Column) parentClass.Columns.GetByIndex(i)).Name;
						fields.Add(key, null);
					}
					// Data will be put into DataSet on Add
				}
				else if(id > 0 && cid == 0 && value != null)
				{
					// Late class binding
					// Assign the fields default values
					fields = new SortedList();
					for(int i = 0; i < value.Columns.Count; i++)
					{
						string key = ((Column) value.Columns.GetByIndex(i)).Name;
						fields.Add(key, null);
					}
					// Modify the dataset
					DataRow row = data.Tables[value.Name].NewRow();
					row[Tree.NODE_ID] = id;
					// The other fields are left blank (or default)
					// Applying
					data.Tables[value.Name].Rows.Add(row);
					// Then values should be edited before commiting
					// In this version we allow nulls or pass defaults
					data.AcceptChanges();
				}
				else if(id > 0 && cid > 0 && value == null)
				{
					// Erase the field data
					fields = null;
					// Remove from dataset
					DataRow[] rows = data.Tables[parentClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length == 1)
						rows[0].Delete();
					data.AcceptChanges();
				}
				/*if(cid != parentClass.Id)
					classChanged = true;*/
				parentClass = value;
				cid = (value == null) ? 0 : parentClass.Id;
			}
		}

		/// <summary>
		/// The tree this node belongs to.
		/// </summary>
		public Tree ParentTree
		{
			get
			{
				return tree;
			}
		}

		/// <summary>
		/// Child nodes.
		/// </summary>
		public Node[] Children
		{
			get
			{
				DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("pid=" + id.ToString());
				Node[] childNodes = new Node[rows.Length];
				for(uint i = 0; i < rows.Length; i++)
				{
					childNodes[i] = new Node(tree, uint.Parse(rows[i]["id"].ToString()));
				}
				return childNodes;
			}
		}

		/// <summary>
		/// Left index.
		/// </summary>
		public uint Left
		{
			get
			{
				return left;
			}
		}

		/// <summary>
		/// Right index.
		/// </summary>
		public uint Right
		{
			get
			{
				return right;
			}
		}

		/// <summary>
		/// Number of levels from root.
		/// </summary>
		public uint Depth
		{
			get
			{
				return depth;
			}
		}

		/// <summary>
		/// Node path.
		/// </summary>
		public string Path
		{
			get
			{
				return path;
			}
		}

		/// <summary>
		/// Tells whether the node is leaf (sealed node).
		/// </summary>
		public bool Leaf
		{
			get
			{
				return leaf;
			}
			set
			{
				leaf = value;
			}
		}

		/// <summary>
		/// Number of child nodes.
		/// </summary>
		public uint ChildCount
		{
			get
			{
				DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("pid=" + id.ToString());
				return (uint) rows.Length;
			}
		}

		/// <summary>
		/// User-defiend fields collection.
		/// </summary>
		public SortedList Fields
		{
			get
			{
				return fields;
			}
		}

		/// <summary>
		/// Adds this node to the tree.
		/// </summary>
		/// <returns>True on success, false on error.</returns>
		public bool Add()
		{
			if(!isNew)
				return false;
			// Adding the node to the node registry
			DataRow row = data.Tables[Tree.NODE_REGISTRY].NewRow();
			id = uint.Parse(row["id"].ToString());
			row["name"] = name;
			row["pid"] = pid;
			row["leaf"] = leaf;
			row["left"] = 0;
			row["right"] = 0;
			if(pid == 0)
			{
				row["depth"] = 0;
				row["path"] = PATH_DELIMITER + id.ToString();
			}
			else
			{
				Node parent = this.Parent;
				row["depth"] = parent.depth + 1;
				row["path"] = parent.path + PATH_DELIMITER + id.ToString();
				if(parent.Leaf)
				{
					parent.Leaf = false;
					parent.Update();
				}
			}
			row["cid"] = cid;
			data.Tables[Tree.NODE_REGISTRY].Rows.Add(row);
			// Adding the node to the class-specific table
			if(cid > 0)
			{
				row = data.Tables[parentClass.Name].NewRow();
				row[Tree.NODE_ID] = id;
				for(int i = 0; i < fields.Count; i++)
				{
					row[fields.GetKey(i).ToString()] = fields[i];
				}
				data.Tables[parentClass.Name].Rows.Add(row);
			}
			// Applying
			data.AcceptChanges();
			if(pid > 0)
				tree.Modified = true;
			isNew = false;
			return RecalcIndeces();
		}

		/// <summary>
		/// Removes this node and all of its children from the tree.
		/// </summary>
		/// <returns>Number of nodes affected.</returns>
		public uint Delete()
		{
			uint res = 0;
			// Removing from the node registry
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("id=" + id.ToString());
			if(rows.Length == 1)
			{
				res++;
				rows[0].Delete();
				// Removing all the child nodes
				rows = data.Tables[Tree.NODE_REGISTRY].Select("left > " + left.ToString() + " AND right < " + right.ToString());
				for(uint i = 0; i < rows.Length; i++)
				{
					rows[i].Delete();
					res++;
				}
				Node parent = this.Parent;
				if(parent.ChildCount == 1)
				{
					parent.Leaf = true;
					parent.Update();
				}
				// Removing from class-specific data storage
				if(cid > 0)
				{
					rows = data.Tables[parentClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length > 0)
						rows[0].Delete();
				}
				// Applying
				data.AcceptChanges();
				tree.Modified = true;
				RecalcIndeces();
			}
			return res;
		}

		/// <summary>
		/// Updates the node in tree dataset.
		/// </summary>
		/// <returns>True on success, false on error.</returns>
		public bool Update()
		{
			// Updating the node registry
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("id=" + id.ToString());
			if(rows.Length == 1)
			{
				rows[0]["pid"] = pid;
				rows[0]["depth"] = depth;
				rows[0]["path"] = path;
				rows[0]["name"] = name;
				rows[0]["leaf"] = leaf;
				rows[0]["cid"] = cid;
				rows[0].AcceptChanges();
				// TODO: Modifying node fields
				// Currently disable due to List bugs. Editing is done in DataGrid only.
				/*if(cid > 0)
				{
					// Update the fields
					rows = data.Tables[parentClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length == 1)
					{
						for(int i = 0; i < fields.Count; i++)
						{
							rows[0][fields.GetKey(i).ToString()] = fields[i];
						}
						rows[0].AcceptChanges();
					}
				}*/
				// Applying
				data.AcceptChanges();
				tree.Modified = true;
				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// Moves this branch to a new parent.
		/// </summary>
		/// <param name="newPid">New node parent identifier.</param>
		/// <returns>True on success, false on error.</returns>
		public bool Move(uint newPid)
		{
			bool res;
			// Check if we are not trying to move to our own child
			Node parent = new Node(tree, newPid);
			if(parent.Left > this.Left && parent.Right < this.Right)
				return false;
			// Make old parent leaf if needed
			if(this.Parent.ChildCount == 1)
			{
				this.Parent.Leaf = true;
				this.Parent.Update();
			}
			// Change pid
			pid = newPid;
			// Make the new parent non-leaf if needed
			if(parent.Leaf)
			{
				parent.Leaf = false;
				parent.Update();
			}
			// Recalc path/depth in this branch
			res = RecalcPath();
			tree.Modified = true;
			RecalcIndeces();
			return res;
		}

		/// <summary>
		/// Auxilliary for Move(), recursive path/depth recalculation.
		/// </summary>
		/// <returns>Procedure result.</returns>
		protected bool RecalcPath()
		{
			Node parent = this.Parent;
			depth = parent.Depth + 1;
			path = parent.Path + PATH_DELIMITER + id.ToString();
			bool res = Update();
			// Now the same for our own children
			Node[] children = this.Children;
			for(uint i = 0; i < children.Length; i++)
				children[i].RecalcPath();
			return res;
		}

		/// <summary>
		/// Refreshes the object due to the changes in the DataSet.
		/// </summary>
		/// <returns>True on success, false on error.</returns>
		public bool Refresh()
		{
			// Refresh the node links
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("id=" + id.ToString());
			if(rows.Length == 1)
			{
				pid = uint.Parse(rows[0]["pid"].ToString());
				depth = uint.Parse(rows[0]["depth"].ToString());
				path = rows[0]["path"].ToString();
				name = rows[0]["name"].ToString();
				leaf = bool.Parse(rows[0]["leaf"].ToString());
				left = uint.Parse(rows[0]["left"].ToString());
				right = uint.Parse(rows[0]["right"].ToString());
				// This actually requires reloading the ParentClass and Fields,
				// but is it ever really used? Masking:
				// cid = uint.Parse(rows[0]["cid"].ToString());
				// Refresh the node contents
				if(cid > 0)
				{
					rows = data.Tables[parentClass.Name].Select(Tree.NODE_ID + "=" + id.ToString());
					if(rows.Length == 1)
					{
						for(int i = 0; i < fields.Count; i++)
						{
							fields[i] = rows[0][fields.GetKey(i).ToString()];
						}
					}
				}
				return true;
			}
			else
				return false;
		}

		/// <summary>
		/// Recalculates left and right indeces in the whole table.
		/// </summary>
		/// <param name="startNodeId">Initial node identifier.</param>
		/// <param name="index">Incremented index.</param>
		/// <returns>True on success, false on error.</returns>
		private bool RecalcIndeces(uint startNodeId, ref uint index)
		{
			bool res = true;
			// Setting self left index
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("id=" + startNodeId.ToString());
			DataRow startRow;
			if(rows.Length == 1)
				startRow = rows[0];
			else
				return false;
			startRow["left"] = index; // Attention: no links here
			index++;
			// Getting all the children and setting their indeces recursively
			rows = data.Tables[Tree.NODE_REGISTRY].Select("pid=" + startRow["id"].ToString());
			for(uint i = 0; i < rows.Length; i++)
			{
				res = res && RecalcIndeces(uint.Parse(rows[i]["id"].ToString()), ref index);
			}
			// Setting self right index
			startRow["right"] = index;
			index++;
			data.Tables[Tree.NODE_REGISTRY].AcceptChanges();
			Refresh();
			return res;
		}

		/// <summary>
		/// Runs left/right index recalculation in the entire tree. 
		/// </summary>
		/// <returns>True on success, false on error.</returns>
		private bool RecalcIndeces()
		{
			uint index = 1;
			return RecalcIndeces(tree.Root.Id, ref index);
		}
		
		/// <summary>
		/// Gets the display grid for the node.
		/// </summary>
		/// <returns>DataView containing the node data.</returns>
		public DataView View()
		{
			if(cid == 0)
				return null;
			DataView result = new DataView(data.Tables[ParentClass.Name]);
			result.RowFilter = Tree.NODE_ID + "=" + id.ToString();
			return result;
		}
		
		/// <summary>
		/// Gets the display grid for the node auxilliary information.
		/// </summary>
		/// <returns>DataView containing the node internal description.</returns>
		public DataView ViewInfo()
		{
			DataView result = new DataView(data.Tables[Tree.NODE_REGISTRY]);
			result.RowFilter = "id=" + id.ToString();
			return result;
		}
		
		/// <summary>
		/// Finds the children of an exact class.
		/// </summary>
		/// <param name="filterClass">A class to pick up.</param>
		/// <returns>DataView containing all the children of specified class.</returns>
		public DataView ViewChildren(Class filterClass)
		{
			if(filterClass == null)
			{
				// No data available for empty classes
				return null;
			}
			string rowFilter = "";
			uint fCid = filterClass.Id;
			DataRow[] rows = data.Tables[Tree.NODE_REGISTRY].Select("cid = " + filterClass.Id.ToString() + " AND left > " + left.ToString() + " AND right < " + right.ToString());
			for(int i = 0; i < rows.Length; i++)
			{
				if(i == 0)
					rowFilter = Tree.NODE_ID + " = " + rows[i]["id"].ToString();
				else
					rowFilter += " OR " + Tree.NODE_ID + " = " + rows[i]["id"].ToString();
			}
			if(rowFilter != "")
			{
				// Some nodes found
				DataView result = new DataView(data.Tables[filterClass.Name]);
				result.RowFilter = rowFilter;
				return result;
			}
			else
			{
				// Nothing found
				return null;
			}
		}
		
		/// <summary>
		/// Finds children relations (auxilliary information) for all the child nodes.
		/// </summary>
		/// <returns>DataView containing all the children auxilliary information.</returns>
		public DataView ViewChildrenInfo()
		{
			return ViewChildrenInfo(null);
		}
		
		/// <summary>
		/// Finds children relations (auxilliary information) for the nodes of exact class.
		/// </summary>
		/// <param name="filterClass">A class to pick up.</param>
		/// <returns>DataView containing all the children auxilliary information for the specified class.</returns>
		public DataView ViewChildrenInfo(Class filterClass)
		{
			DataView result = new DataView(data.Tables[Tree.NODE_REGISTRY]);
			if(filterClass == null)
				result.RowFilter = "left > " + left.ToString() + " AND right < " + right.ToString();
			else
				result.RowFilter = "cid = " + filterClass.Id.ToString() + " AND left > " + left.ToString() + " AND right < " + right.ToString();
			return result;
		}
		
		// Probable TODO methods:
		// Copy()
	}
}
